package x.ovo.wechat.bot.http;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import x.ovo.wechat.bot.common.http.EngineConfig;
import x.ovo.wechat.bot.common.http.HttpEngine;
import x.ovo.wechat.bot.common.http.request.ApiRequest;

import java.io.IOException;
import java.net.HttpCookie;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
public class DefaultHttpEngine implements HttpEngine<OkHttpClient> {

    private OkHttpClient client;
    private OkCookieJar cookieJar;

    public static DefaultHttpEngine create() {
        return create(EngineConfig.create());
    }

    public static DefaultHttpEngine create(EngineConfig config) {
        DefaultHttpEngine engine = new DefaultHttpEngine();
        engine.init(config);
        return engine;
    }

    @Override
    public void init(EngineConfig config) {
        if (null != this.client) {
            return;
        }

        final OkHttpClient.Builder builder = new OkHttpClient.Builder();

        // 连接超时
        final int connectionTimeout = config.getConnectionTimeout();
        if (connectionTimeout > 0) {
            builder.connectTimeout(connectionTimeout, TimeUnit.SECONDS);
        }
        // 读写超时
        final int readTimeout = config.getReadTimeout();
        if (readTimeout > 0) {
            // 读写共用读取超时
            builder.readTimeout(config.getReadTimeout(), TimeUnit.SECONDS)
                    .writeTimeout(config.getWriteTimeout(), TimeUnit.SECONDS);
        }

        // 默认关闭自动跳转
        builder.followRedirects(false);

        this.cookieJar = new OkCookieJar();
        builder.cookieJar(this.cookieJar);
        builder.hostnameVerifier((hostname, session) -> true);

        this.client = builder.build();
    }

    private Request buildRequest(ApiRequest<?> request) {
        final Request.Builder builder = new Request.Builder().url(request.getUrl());

        // 填充方法
        final String method = request.getMethod();
        final Map<String, Object> parameters = request.getParameters();

        // get方法
        if ("GET".equalsIgnoreCase(method)) {
            builder.get();
            builder.url(this.buildUrlQuery(request.getUrl(), parameters));
        } else {
            // 为了兼容支持rest请求，在此不区分是否为GET等方法，一律按照body是否有值填充，兼容
            byte[] bytes = JSONUtil.toJsonStr(parameters).getBytes();

            builder.method(method, RequestBody.create(bytes, MediaType.parse(request.getContentType())));
            builder.url(request.getUrl());
        }

        // 填充头信息
        request.getHeaders().forEach(builder::addHeader);

        return builder.build();
    }

    /**
     * 生成 URL 查询
     *
     * @param url        网址
     * @param parameters 参数
     * @return {@link String }
     */
    private String buildUrlQuery(String url, Map<String, Object> parameters) {
        if (null == parameters || parameters.isEmpty()) return url;
        String prefix = url.contains("?") ? "&" : "?";
        return url + prefix + parameters.entrySet().stream()
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .reduce((a, b) -> a + "&" + b)
                .orElse("");
    }

    @Override
    public <T> T execute(ApiRequest<T> request, Function<String, T> responseHandler) {
        Request buildRequest = this.buildRequest(request);
        if (null == this.client) {
            throw new IllegalStateException("HttpEngine not init");
        }
        try (final Response response = this.client.newCall(buildRequest).execute()) {
            if (response.isSuccessful() && null != response.body()) {
                String string = response.body().string();
                return responseHandler.apply(string);
            } else {
                throw new RuntimeException("request failed, status code: " + response.code());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<HttpCookie> getCookies() {
        return this.cookieJar.getCookies();
    }

    @Override
    public OkHttpClient getRawEngine() {
        return this.client;
    }
}
